﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

using Gurucore.Framework.Core;
using Gurucore.Framework.DataAccess.Persistence.QueryLanguage;

namespace Gurucore.Framework.DataAccess.Persistence
{
	public class TableMapper<T, K> : Query<T> where T : class where K : struct
	{
		static TableMapper()
		{
			DTOProxier oProxier = new DTOProxier();
			oProxier.GetProxyClass<T>();
		}

		internal override string GetQueryTarget()
		{
			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(typeof(T));

			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();
			string sTablePrefix = oTableInfo.UseTablePrefix ? oDACtx.GetTablePrefix() : string.Empty;

			return sTablePrefix + oTableInfo.TableName;
		}

		internal override List<string> GetColumnsList()
		{
			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(typeof(T));

			return oTableInfo.Column;
		}

		internal override T GetSample()
		{
			return default(T);
		}

		internal override DTOTransformer GetBatchTransformer()
		{
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			string sTransformStrategy = oDACfg.TransformStrategy;
			switch (sTransformStrategy)
			{
				case "JIT":
					TransformerClassGenerator oGenerator = new TransformerClassGenerator(typeof(T));
					return oGenerator.GetTransformer();
				default:
					return new SingleDTOTransformer();
			}
		}

		internal virtual SingleDTOTransformer GetSingleTransformer()
		{
			return new SingleDTOTransformer();
		}

		internal virtual string GetPrimaryKey()
		{
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();
			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(typeof(T));

			string sPrefix = oTableInfo.UseTablePrefix ? oDACtx.GetTablePrefix() : string.Empty;

			return sPrefix + oTableInfo.TableName + "." + oTableInfo.PrimaryKey;
		}

		internal virtual void InspectInsertValues(T p_oObject, out List<string> columns, out List<object> values)
		{
			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(typeof(T));

			columns = new List<string>();
			values = new List<object>();

			int i = 0;
			foreach (string sColumn in oTableInfo.ShortColumn)
			{
				string sColumnFullName = oTableInfo.Column[i];

				if (((IManageableDTO)p_oObject).GetColumnState(sColumn) == DTOColumnState.Changed)
				{
					if (!oTableInfo.Identity[sColumnFullName])
					{
						((IManageableDTO)p_oObject).SetColumnState(sColumn, DTOColumnState.Unchanged);
						columns.Add(sColumn);
						values.Add(oTableInfo.Property[sColumnFullName].GetValue(p_oObject, null));
					}
				}
				i++;
			}
		}

		internal virtual K InspectUpdateValues(T p_oObject, out List<string> changedColumns, out List<object> changedValues, out List<string> forcedNullColumns)
		{
			K key = default(K);

			Type oType = typeof(T);

			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(oType);

			changedColumns = new List<string>();
			changedValues = new List<object>();
			forcedNullColumns = new List<string>();

			int i = 0;
			foreach (string sColumn in oTableInfo.ShortColumn)
			{
				string sColumnFullName = oTableInfo.Column[i];

				if (sColumn == oTableInfo.PrimaryKey)
				{
					key = (K)oTableInfo.Property[sColumnFullName].GetValue(p_oObject, null);
				}
				if (!oTableInfo.Identity[sColumnFullName])
				{
					if (((IManageableDTO)p_oObject).GetColumnState(sColumn) == DTOColumnState.Changed)
					{
						((IManageableDTO)p_oObject).SetColumnState(sColumn, DTOColumnState.Unchanged);
						changedColumns.Add(sColumn);
						changedValues.Add(oTableInfo.Property[sColumnFullName].GetValue(p_oObject, null));
					}
					else if (((IManageableDTO)p_oObject).GetColumnState(sColumn) == DTOColumnState.ForceNull)
					{
						((IManageableDTO)p_oObject).SetColumnState(sColumn, DTOColumnState.Unchanged);
						forcedNullColumns.Add(sColumn);
					}
				}
				i++;
			}

			return key;
		}

		internal virtual K InspectPrimaryKey(T p_oObject)
		{
			K key = default(K);

			Type oType = typeof(T);

			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(oType);

			int i = 0;
			foreach (string sColumn in oTableInfo.ShortColumn)
			{
				string sColumnFullName = oTableInfo.Column[i];

				if (sColumn == oTableInfo.PrimaryKey)
				{
					key = (K)oTableInfo.Property[sColumnFullName].GetValue(p_oObject, null);
					break;
				}
			}

			return key;
		}

		public virtual void CreateTable()
		{
		}

		public virtual void DropTable()
		{
		}

		public virtual T Create()
		{
			DTOProxier oProxier = new DTOProxier();
			Type oDTOProxyType = oProxier.GetProxyClass<T>();
			return (T)Activator.CreateInstance(oDTOProxyType);
		}

		public virtual T Select(K p_key, params ColumnOperand[] p_columns) 
		{
			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			//get DTOMaker
			DTOTransformer oDTOMaker = this.GetBatchTransformer();

			//obtain connection
			IDbConnection oDbConn = oDACtx.GetConnection();
			IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

			List<string> fetchedColumns = null;
			if ((p_columns == null) || (p_columns.Length == 0))
			{
				fetchedColumns = GetColumnsList();
			}
			else
			{
				fetchedColumns = new List<string>();
				foreach (ColumnOperand column in p_columns)
				{
					fetchedColumns.Add(column.FullName);
				}
			}

			//prepare query
			oSqlGenerator.PrepareRecordSelectCommand(oDbCmd, this.GetQueryTarget(), this.GetPrimaryKey(), p_key, fetchedColumns);

			//do query
			IDataReader oDataReader = oDbCmd.ExecuteReader();

			//convert IDbDataReader into DTO
			List<T> arrDTOs = oDTOMaker.Transform(oDataReader, this.GetSample());
			oDataReader.Close();
			oDACtx.ReturnConnection(oDbConn);

			//return
			if (arrDTOs.Count != 1)
			{
				return default(T);
			}
			else
			{
				return arrDTOs[0];
			}
		}

		public virtual T Insert(T p_oObject)
		{
			if (p_oObject == null)
			{
				return null;
			}

			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			//get DTOMaker
			SingleDTOTransformer transformer = this.GetSingleTransformer();

			//obtain connection
			IDbConnection oDbConn = oDACtx.GetConnection();
			IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

			List<string> columns = null;
			List<object> values = null;

			this.InspectInsertValues(p_oObject, out columns, out values);

			string sPrimaryKey = this.GetPrimaryKey();

			//prepare query
			string[] arrRetrievedColumn = oSqlGenerator.PrepareInsertCommand(oDbCmd, this.GetQueryTarget(), sPrimaryKey, columns, values);

			//do query
			IDataReader oDataReader = oDbCmd.ExecuteReader();

			oDataReader.Read();

			object inserted = transformer.Transform(typeof(T), p_oObject, oDataReader);

			oDataReader.Close();
			oDACtx.ReturnConnection(oDbConn);

			return (T)inserted;
		}
		
		public virtual int Update(T p_oObject)
		{
			if (p_oObject == null)
			{
				return 0;
			}

			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			List<string> changedColumns = null;
			List<object> changedValues = null;
			List<string> forcedNullColumns = null;

			K key = this.InspectUpdateValues(p_oObject, out changedColumns, out changedValues, out forcedNullColumns);

			if ((changedColumns.Count >= 0) || (forcedNullColumns.Count >= 0))
			{
				//obtain connection
				IDbConnection oDbConn = oDACtx.GetConnection();
				IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

				//prepare query
				oSqlGenerator.PrepareRecordUpdateCommand(oDbCmd, this.GetQueryTarget(), this.GetPrimaryKey(), key, changedColumns, changedValues, forcedNullColumns);

				int nResult = 0;

				nResult = oDbCmd.ExecuteNonQuery();

				//return
				return nResult;
			}
			else
			{
				return 0;
			}
		}

		public virtual int Delete(T p_oObject)
		{
			if (p_oObject == null)
			{
				return 0;
			}

			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			//obtain connection
			IDbConnection oDbConn = oDACtx.GetConnection();
			IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

			K key = this.InspectPrimaryKey(p_oObject);

			//prepare query
			oSqlGenerator.PrepareRecordDeleteCommand(oDbCmd, this.GetQueryTarget(), this.GetPrimaryKey(), key);

			int nResult = oDbCmd.ExecuteNonQuery();

			oDACtx.ReturnConnection(oDbConn);

			//return
			return nResult;
		}

		public override int Update(ColumnOperand[] p_arrColumn, Expression[] p_arrNewValue)
		{
			if (p_arrColumn.Length != p_arrNewValue.Length)
			{
				return 0;
			}

			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			//obtain connection
			IDbConnection oDbConn = oDACtx.GetConnection();
			IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

			List<string> updatedColumns = new List<string>();
			foreach (ColumnOperand column in p_arrColumn)
			{
				updatedColumns.Add(column.ShortName);
			}

			//prepare query
			oSqlGenerator.PrepareBatchUpdateCommand(oDbCmd, this.GetQueryTarget(), m_expFilter, updatedColumns.ToArray(), p_arrNewValue);

			int nResult = oDbCmd.ExecuteNonQuery();

			oDACtx.ReturnConnection(oDbConn);

			//return
			return nResult;		
		}

		public override int Delete()
		{
			//get current data access context
			DataAccessConfiguration oDACfg = Application.GetInstance().GetGlobalSharedObject<DataAccessConfiguration>();
			DataAccessContext oDACtx = DataAccessContext.GetDataAccessContext();

			//get SqlGenerator
			SqlGenerator oSqlGenerator = new SqlGenerator();

			//obtain connection
			IDbConnection oDbConn = oDACtx.GetConnection();
			IDbCommand oDbCmd = oDACtx.GetDbCommand(oDbConn);

			//prepare query
			oSqlGenerator.PrepareBatchDeleteCommand(oDbCmd, this.GetQueryTarget(), m_expFilter);

			int nResult = oDbCmd.ExecuteNonQuery();

			oDACtx.ReturnConnection(oDbConn);

			//return
			return nResult;
		}
	}
}
